﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Threading;
using System.Collections.ObjectModel;
using nRoute.Components;
using nRoute.Components.Composition;

namespace nRoute.Samples.SquareAway.Model
{
    [MapResource(InstanceLifetime.PerInstance)]
    public class Engine
    {
        private const string GAME_NOT_INTIALIZED = "Game not initialized";
        private const string SQUARE_NOTEXIST = "Square does not exist";

        private bool _isCompleted;
        private Action _completedCallback;
        private int _score;
        private int _columns;
        private int _rows;
        private ObservableCollection<Square> _squares;
        private ReadOnlyObservableCollection<Square> _squaresWrapper;

        public Engine() { }

#region Methods

        public void NewGame(int columns, int rows, Action completedCallback)
        {
            if (rows < 1) throw new ArgumentOutOfRangeException("rows");

            // save callback
            _completedCallback = completedCallback;
            _isCompleted = false;
            if (_squares == null) _squares = new ObservableCollection<Square>();
            if (_squaresWrapper == null) _squaresWrapper = new ReadOnlyObservableCollection<Square>(_squares);
            
            // if it is the same layout then re-initialize
            if (_columns == columns && _rows == rows)
            {
                ReInitializeSquares();
            }
            else
            {
                _columns = columns;
                _rows = rows;
                _squares.Clear();
                InitializeSquares();
            }

            _score = 0;
        }

        public void HighlightSquare(Square square)
        {
            if (_isCompleted) return;
            if (square.State == SquareState.Hidden) return;      // this happens in WP7

            // check 
            if (!IsHideableSquare(square)) return;
            SetSquareState(square, new List<Square>(), square.Color, SquareState.Highlighted);
        }

        public void UnHighlightSquare(Square square)
        {
            if (_isCompleted) return; 
            if (square.State == SquareState.Hidden) return;      // this happens in WP7

            SetSquareState(square, new List<Square>(), square.Color, SquareState.Initialized);
        }

        public void HideSquare(Square square)
        {
            if (square == null) throw new ArgumentNullException("square");
            if (Squares == null) throw new InvalidOperationException(GAME_NOT_INTIALIZED);
            if (!Squares.Contains(square)) throw new InvalidOperationException(SQUARE_NOTEXIST);

            // checks
            if (_completedCallback == null) return;
            if (!IsHideableSquare(square)) return;

            // and move the square
            var _hidden = new List<Square>();
            SetSquareState(square, _hidden, square.Color, SquareState.Hidden);
            MoveSquaresVertically();
            MoveSquaresHorizontally();

            // update the score
            _score += Convert.ToInt32(Math.Pow(_hidden.Count, 2));
            
            // callback to say completed
            if (!HasAnyHideableSquare())
            {
                var _squaresCount = _columns * _rows;
                var _remainingSquaresCount = Squares.Count((b) => b.Column != -1);

                _score += (_squaresCount - (_remainingSquaresCount * 3));       // bonus
                if (_remainingSquaresCount == 0) _score += 100;                 // special bonus for clearing all 
                
                _isCompleted = true;
                _completedCallback();
                _completedCallback = null;
            }
        }

#endregion

#region Properties

        public int Columns
        {
            get { return _columns; }
        }

        public int Rows
        {
            get { return _rows; }
        }

        public ReadOnlyObservableCollection<Square> Squares
        {
            get { return _squaresWrapper; }
        }

        public int Score
        {
            get { return _score; }
        }

#endregion

#region Helpers

        private void InitializeSquares()
        {
            // create the board
            var _random = new Random(Convert.ToInt32(DateTime.Now.TimeOfDay.TotalSeconds));
            for (var _c = 0; _c < _columns; _c++)
            {
                for (var _r = 0; _r < _rows; _r++)
                {
                    _squares.Add(new Square()
                    {
                        Column = _c,
                        Row = _r,
                        State = SquareState.UnInitialized,
                        Color = (SquareColor)_random.Next(0, 5)
                    });
                }
            }
        }

        private void ReInitializeSquares()
        {
            // re-initialize the board
            var _index = 0;
            var _random = new Random(Convert.ToInt32(DateTime.Now.TimeOfDay.TotalSeconds));
            for (var _c = 0; _c < _columns; _c++)
            {
                for (var _r = 0; _r < _rows; _r++)
                {
                    _squares[_index].Color = (SquareColor)_random.Next(0, 5);
                    _squares[_index].Row = _r;
                    _squares[_index].Column = _c;
                    _squares[_index].State = SquareState.UnInitialized;
                    _index++;
                }
            }
        }

        private void SetSquareState(Square square, List<Square> selectionStack, SquareColor color, SquareState state)
        {
            if (square.Color != color) return;
            square.State = state;
            selectionStack.Add(square);

            var _leftSquare = GetSquare(square.Column - 1, square.Row);
            if (_leftSquare != null && !selectionStack.Contains(_leftSquare)) SetSquareState(_leftSquare, selectionStack, color, state);

            var _rightSquare = GetSquare(square.Column + 1, square.Row);
            if (_rightSquare != null && !selectionStack.Contains(_rightSquare)) SetSquareState(_rightSquare, selectionStack, color, state);

            var _topSquare = GetSquare(square.Column, square.Row - 1);
            if (_topSquare != null && !selectionStack.Contains(_topSquare)) SetSquareState(_topSquare, selectionStack, color, state);

            var _bottomSquare = GetSquare(square.Column, square.Row + 1);
            if (_bottomSquare != null && !selectionStack.Contains(_bottomSquare)) SetSquareState(_bottomSquare, selectionStack, color, state);

            // we hide it here.. coz if at least one square hidden then we have space there..
            if (state == SquareState.Hidden)
            {
                square.Column = -1;
                square.Row = -1;
                //_squares.Remove(square);
            }
        }

        private bool HasAnyHideableSquare()
        {
            foreach (var _square in Squares)
            {
                if (IsHideableSquare(_square)) return true;    
            }

            return false;
        }

        private bool IsHideableSquare(Square square)
        {
            var _leftSquare = GetSquare(square.Column - 1, square.Row);
            if (_leftSquare != null && _leftSquare.Color == square.Color) return true;

            var _rightSquare = GetSquare(square.Column + 1, square.Row);
            if (_rightSquare != null && _rightSquare.Color == square.Color) return true;

            var _topSquare = GetSquare(square.Column, square.Row - 1);
            if (_topSquare != null && _topSquare.Color == square.Color) return true;

            var _bottomSquare = GetSquare(square.Column, square.Row + 1);
            if (_bottomSquare != null && _bottomSquare.Color == square.Color) return true;

            return false;
        }

        private void MoveSquaresHorizontally()
        {
            for (var _row = 0; _row < Rows; _row++)
            {
                var _col = Columns - 1;
                var _emptyCol = (int?)null;
                while (_col >= 0)
                {
                    var _square = GetSquare(_col, _row);
                    if (_square == null)
                    {
                        if (!_emptyCol.HasValue) _emptyCol = _col;
                        _col--;
                    }
                    else
                    {
                        if (_emptyCol.HasValue)
                        {
                            _square.Column = _emptyCol.Value;
                            _col = _emptyCol.Value;
                            _emptyCol = null;
                        }
                        else
                        {
                            _col--;
                        }
                    }
                }
            }
        }

        private void MoveSquaresVertically()
        {
            for (var _col = 0; _col < Columns; _col++)
            {
                var _row = Rows - 1;
                var _emptyRow = (int?)null;
                while (_row >= 0)
                {
                    var _square = GetSquare(_col, _row);
                    if (_square == null)
                    {
                        if (!_emptyRow.HasValue) _emptyRow = _row;
                        _row--;
                    }
                    else
                    {
                        if (_emptyRow.HasValue)
                        {
                            _square.Row = _emptyRow.Value;
                            _row = _emptyRow.Value;
                            _emptyRow = null;
                        }
                        else
                        {
                            _row--;
                        }
                    }
                }
            }
        }

        private Square GetSquare(int column, int row)
        {
            if (column < 0 || row < 0 || column > Columns || row > Rows) return null;
            return Squares.FirstOrDefault((b) => b.Column == column && b.Row == row);
        }

#endregion

    }
}